package bueu.bexl;

import java.lang.reflect.Array;
import java.util.Collection;

import bueu.bexl.utils.Interval;

import static bueu.bexl.utils.TypeUtils.numberValue;
import static bueu.bexl.utils.TypeUtils.stringValue;


public abstract class BexlExpr {

	private final boolean relative;

	public BexlExpr(boolean relative) {
		this.relative = relative;
	}

	public boolean isRelative() {
		return this.relative;
	}

	public final Object[] list(final Object root) {
		return list(Bexl.DEFAULT_CTX, root);
	}
	
	public final Object[] list(final BexlCtx ctx) {
		return list(ctx, ctx.getRoot());
	}

	public final Object[] list(final BexlCtx ctx, final Object root) {
		final Object res = eval(ctx, root, false, null);
		
		if (res == null) {
			return new Object[0];
		}
		
		if (res.getClass().isArray()){
			return (Object[]) res;
		}
		
		return new Object[] { res };
	}

	public final Object get(final BexlCtx ctx) {
		return get(ctx, ctx.getRoot());
	}

	public final Object get(final Object root) {
		return get(Bexl.DEFAULT_CTX, root);
	}

	public final Object get(final BexlCtx ctx, final Object root) {
		final Object res = eval(ctx, root, false, null);
		
		if (res == null) {
			return null;
		}
		
		if (res.getClass().isArray()) {
			return Array.getLength(res) == 0? null : Array.get(res, 0);
		}
		
		if (res instanceof Collection) {
			final Collection<?> collection = (Collection<?>) res;

			 //Esto es mas rapido q un toArray
			return collection.size() == 0? null : collection.iterator().next();
		}
		
		return res;
	}

	protected abstract Object eval(final BexlCtx ctx, final Object current, final boolean set,
			final Object newValue) throws BexlException;
	
	@SuppressWarnings("unchecked")
	protected static final boolean in(final Object value1, final Object value2) {
		if (value1 == null) {
			return true;
		}

		if (value2 == null) {
			return false;
		}
		
		if (value2 instanceof Interval<?>) {
			return ((Interval<Object>) value2).includes(value1);
		}

		final Object[] array1 = toArray(value1);
		final int length1 = array1.length;
		int cont = 0;
		
		for (int i = 0; i < length1; i++) {
			final Object item = array1[i];
			final Object[] array2 = toArray(value2);
			final int length2 = array2.length;
			
			for (int j = 0; j < length2; j++) {
				if (compare(item, array2[j]) == 0) {
					cont++;
					break;
				}
			}
		}

		return cont == length1;
	}

	@SuppressWarnings("unchecked")
	protected static final int compare(final Object value1, final Object value2) {
		final Object v1 = value1 == null? 0 : value1;
		final Object v2 = value2 == null? 0 : value2;
		
		if (v1 instanceof Number && v2 instanceof Number) {
			final double num1 = numberValue(v1);
			final double num2 = numberValue(v2);
			return  num1 == num2? 0 : (num1 > num2? 1 : -1);
		}
		
		if (v1 instanceof Comparable && v2.getClass() == v1.getClass()) {
			return ((Comparable) v1).compareTo(v2);
		}
		
		if (isArray(value1) && isArray(value2)) {
			final Object[] array1 = toArray(value1);
			final int length1 = array1.length;
			final Object[] array2 = toArray(value2);
			final int length2 = array2.length;
			final int length = length1 > length2? length2 : length1;
			
			for (int i = 0; i < length; i++) {
				final int comp = compare(array1[i], array2[i]);
				
				if (comp != 0) {
					return comp;
				}
			}
			
			return length1 == length2? 0 : (length1 > length2? 1 : -1);
		}
		
		return stringValue(v1).compareTo(stringValue(v2));
	}
	
	protected static final boolean isArray(final Object obj) {
		return obj != null && (obj instanceof Collection || obj.getClass().isArray()); 
	}
	
	protected static final Object[] toArray(final Object obj) {
		if (obj == null) {
			return new Object[0];
		}

		if (obj instanceof Collection) {
			return ((Collection<?>) obj).toArray();
		}
		
		final Class<?> type = obj.getClass();
		
		if (type.isArray()) {
			if (type.getComponentType().isPrimitive()) {
				final int length = Array.getLength(obj);
				final Object[] array = new Object[length];
				
				for (int i = 0; i < length; i++) {
					array[i] = Array.get(obj, i);
				}

				return array;
			}

			return (Object[]) obj;
		}
		
		return new Object[] { obj };
	}
}
